home *** CD-ROM | disk | FTP | other *** search
- /*
- * MSORT.C version 1.0 18-Nov-1986
- *
- * A general-purpose implementation of a merge-sort for linked lists.
- *
- * Written for the Commodore Amiga 1000, version 1.1
- * using Lattice C v3.03 by Davide P. Cervone
- *
- * The merge-sort algorithm takes advantage of the fact that it is
- * easy to combine two sorted lists into one sorted list: just keep
- * taking the smaller item off the top of the two lists and add it
- * to the bottom of the new list until the original lists are empty.
- *
- * The sort algorithm first places each item to be sorted in its
- * own list (one item long). Pairs of these singleton lists are merged
- * in the manner described above, and we are left with half as many lists
- * as before, each two items long. Pairs of these lists are combined to
- * form half as many lists again, each four items long. This process is
- * repeated until only one list remains. This is the final, sorted list.
- *
- * The merge-sort is ideal for linked lists, since it does not require
- * random access look-ups (which are difficult in linked-lists). It does
- * not require extra "work-space", so no memory is wasted. Finally, its
- * result is a simple, linked-list structure, ready to use (unlike some sort
- * methods that result in trees or other structures were it is dificult to
- * get from one item to the next).
- *
- * The merge-sort is fairly efficient. In it's worst case, it takes
- * n * (log(n) - 1) + 1 comparisons to sort n items (where the log()
- * function is the log to base 2). Thus, for 1024 items, the maximum
- * number of comparisons is 1024*(10-1)+1 = 9217, or approximately 9
- * comparisons per item. And that's its worst case! Compare this to
- * a worst case of n*(n-1)/2 for some popular sorts (e.g., the tree sort).
- * The worst case for the tree sort for 1024 items is 523776 compares!
- *
- * In its best case, the merge-sort takes n * log(n) / 2 comparisons.
- * For our example of n=1024, that's 5120 comparisons. For the tree sort,
- * the minimum is approximately n * (log(n) - 2) comparisons, for a
- * minimum of 8192 comparisons for 1024 items.
- *
- * Note that the maximum comparisons that will ever occur with the merge-sort
- * is no more than n more than the minimum number possible with the tree
- * sort.
- *
- * While these numbers look impressive, there is one drawback: the merge-
- * sort is heavily weighted toward its worst case, since the merge sort skips
- * comparisons only when one list runs out of items more than one item before
- * the other. In practice, the average number of comparisons made by the
- * merge-sort is a consistant 96% of the maximum number. This gives you a very
- * acurate estimate of the number of comparisons (hence the time it takes) to
- * sort a list of a particular size. Since the maximum value is so close to
- * the most probable value, you don't have to worry about pathelogic cases
- * slowing down your sort routine (for a tree sort, the "pathelogic" case
- * is a pre-sorted list, which may not be an uncommon case).
- *
- * In a comparison against the tree sort using random data (the kind the
- * tree sort handles best), the merge-sort was found to take 96% of its
- * maximum number of sorts, while the tree sort averaged about 38% more than
- * it's minimum (there was considerably more variation with the tree sort).
- * Overall, the tree sort was found to take a consitant 25% more comparisons
- * than the merge-sort on lists of length 300 items to 10000 items. For
- * 100 items, the difference drops off to about 15%, and for 10 items, the two
- * sorts are about the same, with the merge-sort comming out a little bit
- * ahead (one or two comparisons out of an average of 23).
- *
- * All in all, the merge-sort is an effective, fast, and very useful sort
- * that compares favorably with other sort algorithms.
- */
-
- #include <exec/types.h>
-
- #define ADD_TO_NEWLIST(l) (newlist = (newlist->Next = l))
-
- /*
- * The linked list is a double-linked list (there are pointers to both
- * the previous and the following item). The end of the list is marked
- * by NULL pointers.
- *
- * The pointers should appear at the top of the structure you intend to
- * sort, so if you are sorting a linked list of people's names, you might
- * use a structure like the following in your main program:
- *
- * struct ListItem
- * {
- * struct ListItem *Next;
- * struct ListItem *Prev;
- * char FirstName[30];
- * char MiddleInitial;
- * char LastName[50]
- * };
- *
- * The pointers must be the first fields so that mSort() and Merge() can
- * find the pointers without having to know anything about the structure
- * of the data you are sorting.
- */
-
- /*
- * The #ifndef is so that this module can be #included into the program
- * that calls mSort(), rather than requiring it to be linked in separately.
- * To avoid a conflicting definition error, #define NextList to be the
- * name of the pointer to the previous item as it is declared in your
- * program (for the example given above, user #define NextList Prev).
- * be sure that the #define and the struct ListItem declaration both
- * appear before the #include for this module
- */
-
- #ifndef NextList
- struct ListItem
- {
- struct ListItem *Next;
- struct ListItem *NextList;
- };
- #endif
-
- /*
- * These are the compare and dispose functions passed to mSort() (see
- * mSort() for more details), and are assigned to global variables so that
- * they don't have to be passed to mMerge() every time it is called.
- */
-
- static int (*mCompare)() = NULL;
- static void (*mDispose)() = NULL;
-
- /*
- * mMerge()
- *
- * combines two sorted linked lists into one sorted linked list, using
- * the mCompare() function to determine the sorting, and the dispose()
- * function to remove duplicate values.
- *
- * list1 a pointer to the first linked list
- * list2 a pointer to the second linked list
- */
-
- struct ListItem *
- mMerge(list1,list2)
- struct ListItem *list1, *list2;
- {
- static struct ListItem top_item;
- static struct ListItem *newlist, *temp;
- static int result;
-
- top_item.Next = top_item.NextList = NULL;
- newlist = &top_item;
-
- /*
- * While there are still items in each list, compare the top items in the
- * lists. Link the smaller one to the end of the new, combined list, and
- * pop the item off the top of the old list. If the top items are equal,
- * then add one of them to the new list, and if there is a dispose function,
- * get rid of the other one, otherwise add the second one into the list
- * as well.
- *
- * When one of the lists is exauseted, add any remaining items from
- * the other list onto the end of the result list, and return a pointer
- * to the final, sorted list.
- */
-
- while (list1 != NULL && list2 != NULL)
- {
- result = (*mCompare)(list1,list2);
- if (result < 0)
- {
- ADD_TO_NEWLIST(list1);
- list1 = list1->Next;
- }
- else if (result > 0)
- {
- ADD_TO_NEWLIST(list2);
- list2 = list2->Next;
- }
- else
- {
- ADD_TO_NEWLIST(list1);
- list1 = list1->Next;
- if (mDispose == NULL)
- {
- ADD_TO_NEWLIST(list2);
- list2 = list2->Next;
- } else {
- temp = list2;
- list2 = list2->Next;
- temp->Next = temp->NextList = NULL;
- (*mDispose)(temp);
- }
- }
- }
- if (list1 != NULL) ADD_TO_NEWLIST(list1);
- else if (list2 != NULL) ADD_TO_NEWLIST(list2);
- return(top_item.Next);
- }
-
- /*
- * mSort()
- *
- * sorts a linked list of items of any sort, using a user-provided comparison
- * routine. Duplicate items will be removed if the dispose routine is
- * provided. The result list will be a doubly-linked list (pointers go both
- * forward and backward) that is sorted. mSort() returns a pointer to
- * the top of the result list.
- *
- * cur_item a pointer to the begining of the original, unsorted
- * list.
- * compare() a pointer to a function that compares two
- * ListItems and returns a negative number if
- * the first item is smaller, zero if they are
- * equal, and a positive number if the first
- * item is larger than the second. Compare()
- * should take two parameters, the pointers
- * to the ListItems that are to be compared.
- * dispose() disposes of a duplicate ListItem. Dispose()
- * should accept one parameter, the pointer to
- * the ListItem to be removed. Dispose() should
- * free any memory allocated to the ListItem.
- * This list item will not appear in the final
- * linked list. If dispose in NULL, then
- * duplicate items are retained in the list.
- */
-
- struct ListItem *
- mSort(cur_item,compare,dispose)
- struct ListItem *cur_item;
- int (*compare)();
- void (*dispose)();
- {
- static struct ListItem *first_item, *next_item;
-
- /*
- * Put the compare and dispose routines where mMerge() can find them
- */
- mCompare = compare;
- mDispose = dispose;
-
- first_item = NULL;
- if (cur_item != NULL)
- {
- /*
- * Put each item in the original list into a separate list. Use the
- * NextList field to link the individual lists into a list (a linked list
- * of linked lists). Link the end of the list to the begining so we get
- * a ring rather than a list.
- */
- first_item = cur_item;
- do
- {
- next_item = cur_item->Next;
- cur_item->Next = NULL;
- cur_item->NextList = (next_item != NULL)? next_item : first_item;
- cur_item = next_item;
- } while (cur_item != NULL);
- /*
- * While there's more than one list left in the ring (i.e., the list
- * following the current one is not itself), then merge the current list
- * with the one following it (keep track of the first list after the ones
- * we're combining so we can link the result of the merge back into
- * the ring). Finally, if there were more than two lists in the ring
- * (i.e., if the current list is neither equal to the one preceding it nor
- * equal to the one after the list with which it was combined), then
- * link the result list into the ring, otherwise link the result list
- * to itself (since there were only two lists in the ring, their
- * merger should leave us with only one). Move down the ring, so we
- * can merge the next two lists in the ring.
- */
- while ((cur_item = first_item->NextList) != first_item)
- {
- next_item = cur_item->NextList->NextList;
- cur_item = mMerge(cur_item,cur_item->NextList,compare,dispose);
- if (cur_item == first_item || cur_item == next_item)
- {
- cur_item->NextList = cur_item;
- } else {
- first_item->NextList = cur_item;
- cur_item->NextList = next_item;
- }
- first_item = cur_item;
- }
- /*
- * When there's only one list left, traverse the list, setting the
- * reverse pointers so that the list is properly linked both directions.
- */
- first_item->NextList = NULL;
- while (cur_item->Next != NULL)
- {
- cur_item->Next->NextList = cur_item;
- cur_item = cur_item->Next;
- }
- }
- return(first_item);
- }
-